<template>
  <div
    style="
      max-width: 1200px;
      margin: 32px auto;
      height: 80vh;
      overflow-y: auto;

    "
  >
    <h2>动态表单配置 Demo （支持拖拽排序）</h2>
    <el-card>
      <h4>表单字段配置</h4>
      <!-- 拖拽区域 -->
      <!-- :animation="200" 设置拖拽动画的持续时间（200毫秒）
      handle=".drag-handle"只有带有 drag-handle 类的元素才能触发拖拽
      item-key="id"为每个拖拽项指定唯一标识的字段名
      ghost-class="drag-ghost"设置拖拽过程中"幽灵"元素（半透明预览）的 CSS 类名 -->
      <draggable
        v-model="fields"
        :animation="200"
        handle=".drag-handle"
        item-key="id"
        ghost-class="drag-ghost"
      >
        <template #item="{ element: field, index: idx }">
          <div style="display: flex; align-items: center; margin-bottom: 8px">
            <span
              class="drag-handle"
              style="
                cursor: move;
                margin-right: 6px;
                color: #999;
                font-size: 16px;
              "
              >⠿</span
            >
            <el-input
              v-model="field.label"
              placeholder="字段名"
              style="width: 100px"
            />
            <el-select
              v-model="field.type"
              style="width: 100px; margin: 0 12px"
              @change="handleTypeChange(field)"
            >
              <el-option label="文本" value="input" />
              <el-option label="多行文本" value="textarea" />
              <el-option label="数字" value="number" />
              <el-option label="下拉" value="select" />
              <el-option label="多选Tag" value="multiselect" />
              <el-option label="日期" value="date" />
              <el-option label="开关" value="switch" />
              <el-option label="图片" value="image" />
              <el-option label="文件" value="file" />
              <el-option label="评分" value="rate" />
              <el-option label="级联选择" value="cascader" />
              <el-option label="嵌套表单" value="nested" />
            </el-select>
            <!-- 选项配置 -->
            <el-input
              v-if="['select', 'multiselect', 'cascader'].includes(field.type)"
              v-model="field.optionsText"
              style="width: 100px; margin: 0 8px"
              placeholder="选项/层级,逗号分隔"
            />
            <el-select
              v-model="field.validateType"
              placeholder="校验类型"
              style="width: 120px; margin-left: 8px"
            >
              <el-option label="无" value="" />
              <el-option label="数字" value="number" />
              <el-option label="邮箱" value="email" />
              <el-option label="手机号" value="phone" />
            </el-select>

            <el-input
              v-model="field.pattern"
              placeholder="正则表达式"
              style="width: 160px; margin-left: 8px"
            />

            <el-input
              v-model="field.message"
              placeholder="提示语"
              style="width: 160px; margin-left: 8px"
            />
            <el-switch
              v-model="field.required"
              active-text="必填"
              inactive-text="可选"
              style="margin: 0 8px"
            />
            <el-button @click="fields.splice(idx, 1)" type="danger" size="small"
              >删除</el-button
            >
          </div>
        </template>
      </draggable>
      <el-button
        type="primary"
        plain
        @click="addField"
        icon="el-icon-plus"
        size="small"
        >添加字段</el-button
      >
    </el-card>

    <el-card style="margin-top: 24px">
      <h4>实际表单效果</h4>
      <el-form :model="formData" ref="formRef" label-width="90px">
        <template v-for="field in fields" :key="field.id">
          <!-- 在 Vue 中，当你在 <template> 标签上使用 v-for 时，必须将 key 属性直接放在 <template> 标签上，而不是放在它内部的子元素上。 -->
          <el-form-item
            :label="field.label"
            :prop="field.label"
            v-if="field.type !== 'nested' && isVisible(field)"
            :rules="getRules(field)"
          >
            <!-- 这里v-model和v-bind的解析位于v-model及v-bind.md -->
            <component
              :is="getComponent(field)"
              v-model="formData[field.label]"
              v-bind="getExtraProps(field)"
              :placeholder="'请输入' + field.label"
              style="width: 240px"
              v-if="field.type !== 'cascader'"
            >
              <!-- 修改2：仅在选择器类型中渲染选项 -->
              <template v-if="['select', 'multiselect'].includes(field.type)">
                <el-option
                  v-for="opt in getOptions(field)"
                  :label="opt"
                  :value="opt"
                  :key="opt"
                />
              </template>
            </component>
            <!-- 鉴于el-cascader对比其他组件的特殊性,独立出来解析,具体看el-cascader.md -->
            <el-cascader
              v-else
              v-model="formData[field.label]"
              :options="getOptions(field)"
              :placeholder="'请输入' + field.label"
              :props="{
                checkStrictly: false,
                label: 'label',
                value: 'value',
                children: 'children',
              }"
              style="width: 240px"
            />
          </el-form-item>
          <el-form-item v-if="field.type === 'nested'" :label="field.label">
            <div
              v-for="(item, subIndex) in formData[field.label]"
              :key="subIndex"
              style="
                border: 1px solid #e0e0e0;
                padding: 12px;
                margin-bottom: 12px;
                border-radius: 6px;
              "
            >
              <div
                v-for="subField in field.children"
                :key="subField.label"
                style="margin-bottom: 8px"
              >
                <el-form-item
                  :label="subField.label"
                  :prop="`${field.label}.${subIndex}.${subField.label}`"
                  :rules="
                    subField.required
                      ? [{ required: true, message: '必填' }]
                      : []
                  "
                >
                  <component
                    :is="getComponent(subField)"
                    v-model="formData[field.label][subIndex][subField.label]"
                    v-bind="getExtraProps(subField)"
                    :placeholder="'请输入' + subField.label"
                    style="width: 240px"
                  />
                </el-form-item>
              </div>
              <el-button
                type="danger"
                @click="formData[field.label].splice(subIndex, 1)"
                >删除此项</el-button
              >
            </div>
            <el-button type="primary" @click="addNestedItem(field)"
              >添加一条{{ field.label }}
            </el-button>
          </el-form-item>
        </template>
        <el-form-item>
          <el-button type="primary" @click="submitForm">提交</el-button>
          <el-button @click="resetForm" style="margin-left: 10px"
            >重置</el-button
          >
        </el-form-item>
      </el-form>
      <div v-if="submitResult" style="margin-top: 16px">
        <b>表单结果：</b>
        <pre>{{ submitResult }}</pre>
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, reactive, watch } from "vue";
import draggable from "vuedraggable"; // 必须import
import { ElMessage } from "element-plus";
import { nanoid } from "nanoid"; // 换一个稳定的 key 字段,不然key字段变换会导致dom刷新

function getRules(field) {
  const rules = [];

  if (field.required) {
    rules.push({
      required: true,
      message: `${field.label}为必填项`,
      trigger: "blur",
    });
  }

  // 预设类型校验
  if (field.validateType) {
    const types = {
      email: {
        pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
        message: "请输入正确的邮箱",
      },
      phone: { pattern: /^1[3-9]\d{9}$/, message: "请输入有效手机号" },
      number: { pattern: /^[0-9]+$/, message: "只能输入数字" },
    };
    if (types[field.validateType]) {
      rules.push({
        pattern: types[field.validateType].pattern,
        message: field.message || types[field.validateType].message,
        trigger: "blur",
      });
    }
  }

  // 自定义正则
  if (field.pattern) {
    try {
      const reg = new RegExp(field.pattern);
      rules.push({
        pattern: reg,
        message: field.message || `格式不正确`,
        trigger: "blur",
      });
    } catch (e) {
      console.warn(`无效正则表达式：${field.pattern}`);
    }
  }

  return rules;
}

function isVisible(field) {
  if (!field.visibleWhen) return true;
  const { field: dependField, value } = field.visibleWhen;

  return formData[dependField] === value;
}

// 示例：字段类型改变时自动生成子项
function handleTypeChange(field) {
  if (field.type === "nested" && !field.children) {
    field.children = [
      { label: "子字段1", type: "input", required: false },
      { label: "子字段2", type: "date", required: false },
    ];
  }
}

function addNestedItem(field) {
  if (!formData[field.label]) {
    formData[field.label] = [];
  }

  const newItem = {};
  field.children.forEach((f) => {
    newItem[f.label] = "";
  });

  formData[field.label].push(newItem);
}

// 字段配置（optionsText存字符串,便于编辑）
const fields = ref([
  { id: nanoid(), label: "姓名", type: "input", required: true },
  {
    id: nanoid(),
    label: "性别",
    type: "select",
    required: false,
    optionsText: "男,女",
  },
  { id: nanoid(), label: "开关", type: "switch", required: false },
  { id: nanoid(), label: "评分", type: "rate", required: false },
  { id: nanoid(), label: "头像", type: "image", required: false },
  { id: nanoid(), label: "文件", type: "file", required: false },
  {
    id: nanoid(),
    label: "兴趣",
    type: "multiselect",
    required: false,
    optionsText: "前端,后端,设计,测试",
  },
  {
    id: nanoid(),
    label: "省市区",
    type: "cascader",
    required: false,
    optionsText: "广东/广州/天河,广东/深圳/南山",
  },
  {
    id: nanoid(),
    label: "教育经历",
    type: "nested",
    required: false,
    children: [
      { label: "学校", type: "input", required: true },
      { label: "专业", type: "input", required: true },
      { label: "入学时间", type: "date", required: false },
    ],
  },
  {
    id: nanoid(),
    label: "服兵役情况",
    type: "input",
    required: false,
    visibleWhen: {
      field: "性别", // 依赖的字段
      value: "男", // 条件值
    },
  },
  {
    id: nanoid(),
    label: "邮箱",
    type: "input",
    required: true,
    validateType: "email", // 新增字段：校验类型
    pattern: "", // 允许用户输入正则表达式
    message: "请输入有效邮箱", // 自定义错误信息
  },
]);
// 表单数据
const formData = reactive({});
const formRef = ref();
const submitResult = ref("");

function parseCascaderOptions(optionsText) {
  const paths = optionsText.split(",").map((str) =>
    str
      .split("/")
      .map((s) => s.trim())
      .filter(Boolean)
  );

  const root = [];

  for (const path of paths) {
    let cur = root;
    for (let i = 0; i < path.length; i++) {
      const seg = path[i];
      let node = cur.find((n) => n.label === seg);
      if (!node) {
        node = {
          label: seg,
          value: seg,
        };
        // 只有不是最后一级才添加 children
        if (i < path.length - 1) {
          node.children = [];
        }
        cur.push(node);
      }
      cur = node.children || [];
    }
  }

  return root;
}

// 动态生成 option 数组
function getOptions(field) {
  if (!field.optionsText) return [];
  // 级联格式特殊
  if (field.type === "cascader") {
    return parseCascaderOptions(field.optionsText);
  }
  return field.optionsText
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
}

// 添加字段
function addField() {
  fields.value.push({
    id: nanoid(),
    label: "新字段",
    type: "input",
    required: false,
  });
}

// 组件类型映射
function getComponent(field) {
  switch (field.type) {
    case "input":
      return "el-input";
    case "textarea":
      return "el-input";
    case "number":
      return "el-input-number";
    case "select":
      return "el-select";
    case "multiselect":
      return "el-select";
    case "date":
      return "el-date-picker";
    case "switch":
      return "el-switch";
    case "rate":
      return "el-rate";
    case "image":
      return "el-upload";
    case "file":
      return "el-upload";
    case "cascader":
      return "el-cascader";
    default:
      return "el-input";
  }
}

// 额外属性/插槽
function getExtraProps(field) {
  switch (field.type) {
    case "textarea":
      return { type: "textarea", rows: 3 };
    case "select":
      return {};
    case "multiselect":
      return { multiple: true, collapseTags: true };
    case "date":
      return { type: "date", format: "YYYY-MM-DD" };
    case "image":
      return {
        action: "/api/upload-image",
        name: "file", // multer.single("file") 中的字段名
        listType: "picture-card",
        showFileList: true,
        limit: 1,
        onPreview: (file) =>
          window.open(file.url || file.response?.url, "_blank"),
        beforeUpload: (file) => {
          if (!file.type.startsWith("image/")) {
            ElMessage.error("只能上传图片");
            return false;
          }
          return true;
        },
        // 上传完成后自动设置 formData[field.label] 为返回的 url
        onSuccess: (res, file) => {
          // 假设后端返回：{ url: 'http://localhost:3001/uploads/xxx.jpg' }
          formData[field.label] = res.url;
          file.url = res.url; // 用于图片预览显示
        },
      };
    case "file":
      return {
        action: "/api/upload-image",
        name: "file", // multer.single("file") 中的字段名
        listType: "picture-card",
        showFileList: true,
        limit: 1,
        onPreview: (file) =>
          window.open(file.url || file.response?.url, "_blank"),
        beforeUpload: (file) => {
          if (!file.type.startsWith("image/")) {
            ElMessage.error("只能上传图片");
            return false;
          }
          return true;
        },
        // 上传完成后自动设置 formData[field.label] 为返回的 url
        onSuccess: (res, file) => {
          // 假设后端返回：{ url: 'http://localhost:3001/uploads/xxx.jpg' }
          formData[field.label] = res.url;
          file.url = res.url; // 用于图片预览显示
        },
      };
    case "rate":
      return {};
    case "switch":
      return {};
    case "cascader":
      return { options: getOptions(field), props: { checkStrictly: false } };
    default:
      return {};
  }
}

// 提交
function submitForm() {
  formRef.value.validate((valid) => {
    if (valid) {
      submitResult.value = JSON.stringify(formData, null, 2);
    }
  });
}
// 重置
function resetForm() {
  Object.keys(formData).forEach((key) => (formData[key] = ""));
  submitResult.value = "";
}
</script>
<style scoped>
.drag-handle {
  user-select: none;
  cursor: grab;
}
.drag-ghost {
  opacity: 0.5;
  background: #f0f0f0;
}
</style>
